home account info subscribe login search FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
To access the contents, click the chapter and section titles.

Bug Proofing Visual Basic: A Guide to Error Handling and Prevention
(Publisher: John Wiley & Sons, Inc.)
Author(s): Rod Stephens
ISBN: 0471323519
Publication Date: 11/01/98

Search this book:
 
Previous Table of Contents Next


CHAPTER 10
Encapsulation

Encapsulation is the process of wrapping up a set of features or behavior in a neat package to hide complexity from other parts of a program. For example, an Employee class might represent employee objects. The class might contain elaborate data structures and complicated subroutines. The rest of the program, however, can treat employee objects in a fairly simple way.

Many object-oriented programmers think of encapsulation as a new idea that applies only to classes and objects. Actually, many other programming constructs have provided encapsulation for decades.

This chapter explains some of the ways you can encapsulate data and behavior in a program to reduce the program’s complexity. Reduced complexity makes the program easier to implement, debug, and maintain over time.

Hide Internals

Never write a routine that relies on the internals of another routine or class. If a routine relies on the way another routine works, modifying the second may break the first. If you need to fix a bug in one, you may introduce a new bug in the other.

Keeping track of the way different classes and routines interact with each other can be difficult. Minimize the interactions by keeping the classes and routines as tightly encapsulated as possible. They should not expose anything outside their own code unless absolutely necessary.

Encapsulate with Classes

Use classes to encapsulate functionality, and hide implementation details inside the class. Public routines and property procedures let the rest of the program access the features of the class without knowing the details of how the class works internally. All of the details of how the class stores and manipulates its data should be kept private within the class.

For instance, suppose an EmployeeList class stores a list of employees in an array. It provides a FindEmployee routine that takes an employee’s name as a parameter and returns the employee’s index in the array. The main program can then access the array to learn more about the employee.

This class provides weak encapsulation. The main program knows that the employee information is stored in an array and it can directly access the array. As the program grows, you may decide it would be more efficient to store the employee information in a tree-data structure. Making this change would require you to rewrite all the parts of the program that previously accessed the data using the array.

Now consider a different version of the class. It provides a routine that locates an employee and returns a user-defined data type holding all of the information about that employee. This class provides better encapsulation for the employee list. The main program does not know how the list is stored or accessed, and it does not access any of the class data structures directly. If you change the way this class stores its information, you will not need to change the rest of the program.

Count Object Creation and Destruction

Count the objects created and destroyed by a program by placing code in Class_Initialize and Class_Terminate event handlers. Before the program ends, check the object counts. If a count is not 0, some object was created that was not destroyed. This could indicate a bug or a memory leak.

The following code shows a Cell class. Each Cell object includes a reference to another Cell. The variable NumCells is declared publicly in a .BAS module and is used to count Cell object creation and destruction.

‘ The next cell in the cell’s linked list.
Public NextCell As Cell

Private Sub Class_Initialize()
    NumCells = NumCells + 1
End Sub

Private Sub Class_Terminate()
    NumCells = NumCells - 1
End Sub

Visual Basic performs automatic reference counting. When you create an object, Visual Basic automatically keeps track of the number of variables referencing that object. If the reference count ever reaches 0, the system knows that the program can no longer access the object, so Visual Basic automatically destroys it.

Suppose the program creates a Cell object and sets its NextCell pointer to itself.

Dim new_cell As New Cell

    Set new_cell.NextCell = new_cell
    Set new_cell = Nothing

Because this object contains a reference to itself, its reference count is not 0, even after the variable new_cell is set to Nothing. The Cell object that new_cell used to point to still points to itself, so its reference count is 1 and Visual Basic cannot destroy it. This is one of the few ways you can create a memory leak using Visual Basic code. If the program executes this code many times, it may use up a lot of memory. Eventually, the program may use up all of the available memory and crash.

However, if you check the value of NumCells before the program ends, you will find there are cells that were created but never destroyed. Once you know the problem exists, you can start hunting for the cells. Instead of having an enigmatic crash, you have a simple bug.

This is a trivial example and is unlikely to happen in a real program. More complex data structures such as balanced trees, sparse arrays, and networks are much more likely to cause this sort of problem.

I often use this object-counting technique to verify that complex data structures have been properly destroyed. Occasionally, I have been surprised to find that a data structure that I thought was properly cleaned up had not been completely destroyed. That let me find the bug quickly and painlessly.

Initialize Objects

Classes provide an Initialize event that fires when an object is created. Initialize the object’s state in the Initialize event handler if possible. That way, you can never forget to perform the initialization.

Unfortunately, the Initialize event handler does not take parameters so it can only perform initialization that is the same for every object in the class. If you need to perform object-specific initialization, create a public InitializeObject routine to do so. Name the initialization routine for every class InitializeObject so it is obvious what routine to call. The program should use InitializeObject to initialize each object immediately after creating it.

If you need this kind of initialization, give the class a private Boolean variable Initialized. The InitializeObject routine should set this value to True. All other public routines and property procedures should assert that the control has been initialized before they provide access to the object as shown in the following code.

Private Initialized As Boolean
Private TreeRoot As Boolean

    :
‘ Save the tree’s root for later use.
Public Sub InitializeObject(tree_root As TreeNode)
    Set TreeRoot = tree_root

    Initialized = True
End Sub

‘ Draw this node and those under it.
Public Sub DrawSubTree()
    ‘ Make sure we’re initialized.
    Debug.Assert Initialized
        :
End Sub

If the program tries to use an object before it is properly initialized, the program’s Assert statement makes the bug immediately obvious.


Previous Table of Contents Next


Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-1999 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permision of EarthWeb is prohibited.